package com.dtflys.easyel.runtime;

import com.dtflys.easyel.exception.EasyElAmbiguousMethodsException;

import java.util.LinkedList;
import java.util.List;

/**
 * @author gongjun[jun.gong@thebeastshop.com]
 * @since v1.0.0
 */
public class SameNameMethodCollection {

    private final String methodName;

    private List<MetaMethod> methods = new LinkedList<>();

    public SameNameMethodCollection(String methodName) {
        this.methodName = methodName;
    }


    public void addMethod(MetaMethod method) {
        this.methods.add(method);
    }

    public MetaMethod getSameParamTypesMethod(Class[] paramTypes) {
        for (MetaMethod metaMethod : methods) {
            if (metaMethod.getParamTypes().equalsTypes(paramTypes)) {
                return metaMethod;
            }
        }
        return null;
    }

    public MetaMethod chooseMethod(Class clazz, Object[] arguments) throws EasyElAmbiguousMethodsException {
        int methodLen = methods.size();
        if (methodLen == 0) return null;
        if (methodLen == 1) {
            MetaMethod metaMethod = methods.get(0);
            if (metaMethod.getParamTypes().isValidArguments(arguments)) {
                return metaMethod;
            } else {
                return null;
            }
        }
        MetaMethod ret = null;
        long matchingDistance = -1;
        boolean hasAmbiguousMethods = false;
        List<MetaMethod> ambiguousMethods = new LinkedList<>();
        for (MetaMethod metaMethod : methods) {
            MethodParameterTypes parameterTypes = metaMethod.getParamTypes();
            if (!parameterTypes.isValidArguments(arguments)) {
                continue;
            }
            long distance = parameterTypes.calculateDistanceWithArguments(arguments);
            if (distance == 0) {
                return metaMethod;
            }
            if (ret == null || distance < matchingDistance) {
                ret = metaMethod;
                matchingDistance = distance;
                hasAmbiguousMethods = false;
//                ambiguousMethods.clear();
            } else if (distance == matchingDistance) {
                if (ambiguousMethods.isEmpty()) {
                    ambiguousMethods.add(ret);
                }
                ambiguousMethods.add(metaMethod);
                hasAmbiguousMethods = true;
            }
        }

        if (hasAmbiguousMethods) {
            StringBuilder msgBuilder = new StringBuilder();
            msgBuilder.append("Ambiguous method \"");
            msgBuilder.append(clazz.getName());
            msgBuilder.append(".").append(methodName);
            msgBuilder.append("(");
            for (int i = 0; i < arguments.length; i++) {
                Object arg = arguments[i];
                if (arg == null) {
                    msgBuilder.append("null");
                } else {
                    msgBuilder.append(arg.getClass().getSimpleName());
                }
                if (i < arguments.length - 1) {
                    msgBuilder.append(", ");
                }
            }
            msgBuilder.append(")");
            msgBuilder.append("\"\n");

            for (MetaMethod method : ambiguousMethods) {
                msgBuilder.append("\n");
                msgBuilder.append("                ");
                msgBuilder.append(clazz.getName());
                msgBuilder.append('.');
                msgBuilder.append(method.getMethodDeclarationName());
            }

            throw new EasyElAmbiguousMethodsException(msgBuilder.toString());
        }

        return ret;
    }

    public String getMethodName() {
        return methodName;
    }
}
